#include "rasterizer.h"
#include "init.h"
#include "our_gl.h"
#include "shader.h"
#include <filesystem>
#include <iostream>
#include <QImage>

#define DRAW_LINE_VERSION1 1
#define DRAW_LINE_VERSION2 2
#define DRAW_LINE_VERSION3 3
#define DRAW_LINE_VERSION4 4
#define DRAW_LINE_VERSION5 5

#define USE_DRAW_LINE_VERSION DRAW_LINE_VERSION5

namespace raster {

using namespace mathengine;
namespace fs = std::filesystem;

static const Vec3f default_light_dir{1, 1, 1};
static const Vec3f default_eye{0, -1, 3};
static const Vec3f default_center{0, 0, 0};
static const Vec3f default_up{0, 1, 0};

static const int   default_depth = 255;

// const int depth = 2000;
// const int depth = 255;

// get barycentric coordinate of P with cross product
// P = (1- ux/uz - uy/uz) A + ux/uz B + uy/uz C, uz!=0
//
// vec(u) = (ux,uy,uz) = vec(s1) x vec(s2)
//        = (Bx-Ax,Cx-Ax,Ax-Px) x (By-Ay,Cy-Ay,Ay-Py)
// Pay attention the order of ux, uy, uz
//
// https://www.cnblogs.com/fortunely/p/18180929
static Vec3f barycentric(Vec2f A, Vec2f B, Vec2f C, Vec2f P) {
    auto [Ax, Bx, Cx, Px] = std::make_tuple(A.x(), B.x(), C.x(), P.x());
    auto [Ay, By, Cy, Py] = std::make_tuple(A.y(), B.y(), C.y(), P.y());
    Vec3f s1{ Bx-Ax, Cx-Ax, Ax-Px };
    Vec3f s2{ By-Ay, Cy-Ay, Ay-Py };
    Vec3f u = crossProduct(s1, s2);
    if (std::abs(u[2]) > 1e-2) {
        return Vec3f{1.f - (u.x() + u.y()) / u.z(), u.x() / u.z(), u.y() / u.z()};}
    return Vec3f{-1, 1, 1};
}

Rasterizer::Rasterizer(int w, int h, QImage &image)
    : width(w), height(h), framebuffer_(image),
    lightDir_(default_light_dir), eye_(default_eye), center_(default_center), up_(default_up), depth_(default_depth) {

    init();
    fs::path inFilePath = inPath / "african_head.obj";
    model_ = std::make_shared<Model>(inFilePath.string().c_str());

    zbuffer_.resize(w * h, std::numeric_limits<float>::lowest());
    context_.setModel(Matrix44f::identity());
    context_.lookAt(eye_, center_, up_);
    context_.projection(-1.f / (eye_ - center_).length());
    context_.viewport(0, 0, width, height, depth_);
    lightDir_.normalize();
}

void Rasterizer::drawScene() {
    // GouraudShader shader;
    PhongShader shader(context_.getMVP(), context_.getMVP().invert_transpose());
    for (int i = 0; i < model_->nfaces(); i++) {
        Vec4f screen_coords[3];
        for (int j = 0; j < 3; j++) {
            screen_coords[j] = shader.vertex(this, i, j);
        }
        drawTriangle(screen_coords, shader);
    }

    flipVertically();
    saveAsTGAFile(); // zbuffer image
}

// version 5
// eliminate floating point operations based on version 4
void Rasterizer::drawLine(const Vec2f &p0, const Vec2f &p1, const TGAColor &color) {
    int x0 = p0.x(), y0 = p0.y();
    int x1 = p1.x(), y1 = p1.y();
    bool steep = false;
    if (std::abs(x1 - x0) < std::abs(y1 - y0)) {
        std::swap(x0, y0);
        std::swap(x1, y1);
        steep = true;
    }
    if (x0 > x1) { // make sure x0 <= x1
        std::swap(x0, x1);
        std::swap(y0, y1);
    }

    int dx = x1 - x0;
    int dy = y1 - y0;
    int derror2 = std::abs(dy) * 2; // derror2 = 2 * derror * dx
    int error2 = 0; // error2 = 2 * error * dx
    int y = y0;

    for (int x = x0; x <= x1; x++) {
        if (steep) {
            setPixel(y, x, color);
        } else {
            setPixel(x, y, color);
        }
        error2 += derror2;
        if (error2 > dx) {
            y += (y1 > y0 ? 1 : -1);
            error2 -= dx * 2;
        }
    }
}

void Rasterizer::drawTriangle(const Vec4f *pts, IShader &shader) {
    Vec2f bboxmin{ std::numeric_limits<float>::max(),    std::numeric_limits<float>::max() };
    Vec2f bboxmax{ std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest() };
    Vec2f clamp{ static_cast<float>(framebuffer_.width() - 1), static_cast<float>(framebuffer_.height() - 1) };
    if (!pts) return;

    for (int i = 0; i < 3; i++) { // triangle 0,1,2
        for (int j = 0; j < 2; j++) { // x,y
            bboxmin[j] = std::max(0.f,      std::min(bboxmin[j], pts[i][j]/pts[i].w()));
            bboxmax[j] = std::min(clamp[j], std::max(bboxmax[j], pts[i][j]/pts[i].w()));
        }
    }

    Vec2i P;
    TGAColor color;
    for (P[0] = bboxmin.x(); P[0] <= bboxmax.x(); P[0]++) {
        for (P[1] = bboxmin.y(); P[1] <= bboxmax.y(); P[1]++) {
            // P's barycentric coordinate(alpha, beta, gamma) of the triangle
            Vec3f c = barycentric(proj<float, 4, 2>(pts[0]/pts[0].w()),
                                  proj<float, 4, 2>(pts[1]/pts[1].w()),
                                  proj<float, 4, 2>(pts[2]/pts[2].w()),
                                  P);
            const auto [alpha, beta, gamma] = std::make_tuple(c.x(), c.y(), c.z());
            // Pz = Az * alpha + Bz * beta + Cz * gamma
            float z = pts[0].z() * alpha + pts[1].z() * beta + pts[2].z() * gamma;
            // Pw = Aw * alpha + Bw * beta + Cw * gamma
            float w = pts[0].w() * alpha + pts[1].w() * beta + pts[2].w() * gamma;
            int frag_depth = std::max(0, std::min(depth_, static_cast<int>(z / w + .5f)));
            int zb_index = index(P.x(), P.y());
            if (zb_index >= zbuffer_.size()) {
                std::cout << "zb_index: " << zb_index << ", zbuffer_.size: " << zbuffer_.size() << std::endl;
            }
            auto zb = zbuffer_[zb_index];
            if (alpha < 0 || beta < 0 || gamma < 0 || zb > frag_depth) {
                continue;
            }
            bool discard = shader.fragment(this, c, color);
            if (!discard) {
                zbuffer_[zb_index] = frag_depth;
                setPixel(P.x(), P.y(), color);
            }
        }
    }
}

void Rasterizer::setPixel(int x, int y, const TGAColor &color) {
    if (x >= 0 && x < framebuffer_.width() && y >= 0 && y < framebuffer_.height()) {
        QColor c(color.r, color.g, color.b);
        if (color.bytespp >= 4) {
            c.setAlpha(color.a);
        }
        framebuffer_.setPixelColor(x, y, c);
    }
}

void Rasterizer::clearZBuffer() {
    // zbuffer_.assign(framebuffer_.width() * framebuffer_.height(), std::numeric_limits<float>::infinity());
    zbuffer_.assign(width * height, std::numeric_limits<float>::lowest());
}

void Rasterizer::clear(const TGAColor &color) {
    framebuffer_.fill(QColor(color.r, color.g, color.b, color.a));
    clearZBuffer();
}

const QImage &Rasterizer::getColorBuffer() const {
    return framebuffer_;
}

std::shared_ptr<Model> Rasterizer::currentModel() {
    return model_;
}

RenderContext Rasterizer::currentRenderContext() {
    return context_;
}

Vec3f Rasterizer::eyePos() {
    return eye_;
}

Vec3f Rasterizer::lightDir() {
    return lightDir_;
}

void Rasterizer::setEyePos(Vec3f pos) {
    eye_ = pos;
    updateContext();
}

void Rasterizer::setLightDir(Vec3f dir) {
    lightDir_ = dir.normalized();
    updateContext();
}

void Rasterizer::viewport(int x, int y, int w, int h) {
    context_.viewport(x, y, w, h, depth_);
}

void Rasterizer::flipHorizontally() {
    framebuffer_.flip(Qt::Horizontal);
}

void Rasterizer::flipVertically() {
    framebuffer_.flip(Qt::Vertical);
}

void Rasterizer::saveAsTGAFile() {
    {
        fs::path outFilePath = outPath / "zbuffer.tga";
        TGAImage zbimage(width, height, TGAImage::GRAYSCALE);
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                int idx = i+j*width;
                if (idx >= zbuffer_.size()) {
                    std::cout << "error " << std::endl;
                }
                zbimage.set(i, j, TGAColor(zbuffer_[i+j*width]));
            }
        }
        zbimage.flipVertically();
        zbimage.writeTgaFile(outFilePath.string().c_str());
        std::cout << "z-buffer image saved to " << outFilePath << std::endl;
    }
}

int Rasterizer::index(int x, int y) const {
    return y * framebuffer_.width() + x;
}

void Rasterizer::updateContext() {
    context_.setModel(Matrix44f::identity());
    context_.lookAt(eye_, center_, up_);
    context_.projection(-1.f / (eye_ - center_).length());
    context_.viewport(0, 0, width, height);
}

} // namespace raster
